#include "level.h"

#include "line.h"
#include "sector.h"
#include "room.h"
#include "actor.h" // cli_parse::EntryPoint

#include "structs.h"
#include "util.h"

#include "tesselation.h"

#include "../states.h"
#include "../console.h"

#include "../../Epsilon/debug.h" // eMessageBox
#include "../../Epsilon/Epsilon3D/immediate.h"

#define CULL_BOUNDARIES

using namespace Underlight;

Underlight::Level* Underlight::lvl = NULL;
std::vector<Level*> lvls (0);

Room*
Level::RoomFromSector (int id) const
{
  for (unsigned int i = 0; i < rooms.size (); i++) {
    if (rooms [i]->ContainsSector (id)) {
      return rooms [i];
    }
  }

  return NULL;
}

Room* 
Level::GetRoom (int id)
{
  for (unsigned int i = 0; i < rooms.size (); i++) {
    if (rooms [i]->id == id) {
      return rooms [i];
    }
  }

  return NULL;
}

#include "../../Epsilon/STL.h"
bool
Level::IsTextureLoaded (short id)
{
  std::vector<short>::const_iterator tex = textures.begin ();
  std::vector<short>::const_iterator end = textures.end   ();

  while (eSTL::IterValid (tex, end)) {
    if ((*tex) == id)
      return true;

    tex++;
  }

  return false;
}

bool
Level::IsFlatTexLoaded (short id)
{
  std::vector<short>::const_iterator flat = flats.begin ();
  std::vector<short>::const_iterator end  = flats.end   ();

  while (eSTL::IterValid (flat, end)) {
    if ((*flat) == id)
      return true;

    flat++;
  }

  return false;
}

#include "effect.h"

#include <exception>
#include <new>

bool
Level::Load (void* data, FileLevel& level)
{
  lvl = this;

  if (data == NULL)
    return false;

  _lvl = level;

  unsigned int idx = 0;

  id = _lvl._header.level_id;
  strcpy (name, _lvl._header.name);

  FileSector*  pSector  = (FileSector *)data;
  VertexData*  pVertex  = (VertexData *)( pSector  + _lvl._data.num_sectors);
  FileLinedef* pLinedef = (FileLinedef *)(pVertex  + _lvl._data.num_vertices);
  Actor*       pActor   = (Actor *)(      pLinedef + _lvl._data.num_lines);
  FileRoom*    pRoom    = (FileRoom *)(   pActor   + _lvl._data.num_ornaments);

  for (idx = 0; idx < level._data.num_rooms; idx++) {
    decrypt (pRoom->name, ROOM_NAME_SIZE);

    Room* room = new Room ();
    room->id                 = pRoom->id;
    strncpy (room->name, pRoom->name, ROOM_NAME_SIZE);
    room->background         = pRoom->background;
    room->background_palette = pRoom->background_palette;
    room->flags              = pRoom->flags;

    if (states->cl_verbose) {
      eTB_Printf ("Room %2d:\n", pRoom->id);

      eTB_Printf ("\t>> Name: %s\n",  pRoom->name);

      if (pRoom->background)
        eTB_Printf ("\t>> Sky: %d\n",   pRoom->background);

      if (pRoom->flags) {
        eTB_Printf ("\t>> Flags: %d - ", pRoom->flags);
      }

      eTB_Printf ("\n");
    }

    AddRoom (room);

    pRoom++;
  }

  sectors.resize (level._data.num_sectors);

  for (idx = 0; idx < level._data.num_sectors; idx++) {
    Sector* sector = new Sector ();

    sector->id            = pSector->id;
    sector->light_level   = pSector->lightlevel;
    sector->height_offset = pSector->height_offset;

    sector->ceiling_height = pSector->ceiling_height;
    sector->floor_height   = pSector->floor_height;

    sector->ceiling_bitmap = pSector->ceiling_bitmap;
    sector->floor_bitmap   = pSector->floor_bitmap;

    sector->xanim_bottom = pSector->xanim_bottom;
    sector->xanim_top    = pSector->xanim_top;
    sector->yanim_bottom = pSector->yanim_bottom;
    sector->yanim_top    = pSector->yanim_top;


    //
    // The Underlight file format stores slopes using two 8-bit values;
    //  The angle (0-180 ???) and a fraction.
    // 
    //     ... it saves four bytes per sector; this
    //
    sector->floor_slope_angle = (float) (pSector->floor_slope_angle +
                                         pSector->floor_slope_frac * 0.01f);

    sector->ceiling_slope_angle = (float) (pSector->ceiling_slope_angle +
                                           pSector->ceiling_slope_frac * 0.01f);

    sector->flags = pSector->flags;

    //sector.tag = pSector->tag;

    Room* room = GetRoom (pSector->room);

    if (! room) {
      eTB_Printf ("Sector: %d does not belong to any rooms!\n", idx);
      pSector++;
      continue;
    }

    if (sector->floor_slope_angle)
      sector->floor = new Surface (sector->floor_height, sector->floor_slope_angle, sector->flags & SECTOR_NO_STRETCH_FLOOR);

    if (sector->ceiling_slope_angle &&
        ! (sector->flags & SECTOR_SKY)) // Only slope ceiling if it not sky
      sector->ceil = new Surface (sector->ceiling_height, -sector->ceiling_slope_angle, sector->flags & SECTOR_NO_STRETCH_CEILING);

    room->AddSector (sector);

    AddFlatTex (pSector->ceiling_bitmap);
    AddFlatTex (pSector->floor_bitmap);

    AddSector (sector);

    pSector++;
  }

  for (idx = 0; idx < level._data.num_lines; idx++) {
    Linedef linedef;

    linedef.from = pLinedef->from;
    linedef.to   = pLinedef->to;

    //linedef.flags = pLinedef->flags;

    Room* room = RoomFromSector (pLinedef->sector);

    if (room) {
      room->bound.AddPoint (pVertex [linedef.from].x, pVertex [linedef.from].y);
      room->bound.AddPoint (pVertex [linedef.to].x,   pVertex [linedef.to].y);
    } else {
      eTB_Printf ("Line: %d does not belong to any rooms!\n", idx);
      pLinedef++;
      continue;
    }

    Line* line =
      CreateLine ( pLinedef,
                     this, true,
                       pVertex [linedef.to],
                         pVertex [linedef.from],
                           NULL,
                             idx );

    if (pLinedef->sector != pLinedef->facingsector)
      Line* new_line = CreateLine ( pLinedef,
                                      this, false,
                                        pVertex [linedef.to],
                                          pVertex [linedef.from],
                                            line,
                                              -idx );

    // TODO: load these on-demand, rather than on construction...
    AddTexture (pLinedef->cwall_bitmap);
    AddTexture (pLinedef->fwall_bitmap);

    pLinedef++;
  }

  // NOTE: Must do this last, so we can determine the room/sector for an actor.
  for (idx = 0; idx < level._data.num_ornaments; idx++) {
    cli_parse::Actor* actor = NULL;

    ///int   parent_sector = FindSector     (pActor->x, pActor->y, 0, true);
    ///Room* room          = RoomFromSector (parent_sector);
    Sector* parent_sector = FindSector (pActor->x, pActor->y, NULL, true);
    Room*   room          = room_from_sector (parent_sector);

    // Entry Point
    if (pActor->flags & ACTOR_ENTRYPOINT) {
      cli_parse::EntryPoint* entry = new cli_parse::EntryPoint (idx, pActor->x, pActor->y, pActor->angle, room->id, pActor->finfo);

      switch (entry->GetType ()) {
        case 0:
          //eTB_Printf ("Entrypoint: (%#+6.1f, %#+6.1f : Room: %d) [Normal]\n",   entry->GetX (), entry->GetY (), entry->GetRoom ());
          break;
        case 1:
          //eTB_Printf ("Entrypoint: (%#+6.1f, %#+6.1f : Room: %d) [Tutorial]\n", entry->GetX (), entry->GetY (), entry->GetRoom ());
          break;
      }

      entry_points.push_back (entry);
    }

    // All other actors are ornaments...
    else {
      ////Sector* sector = room->GetSector (parent_sector);
      Sector* sector = parent_sector;

      VisualEffectHeader* effect = NULL;

      // Recall Point
      if (pActor->flags & ACTOR_RECALLPOINT)
        effect = effects->GetBitmapHeader (791);

      // Item Gen
      else if ((pActor->flags & ACTOR_ITEMGENERATOR))
        effect = effects->GetBitmapHeader (792);

      // Agent Gen (spawn point)
      else if (pActor->flags & ACTOR_AGENTGENERATOR)
        effect = effects->GetBitmapHeader (793);

      // Generic
      else
        effect = effects->GetBitmapHeader (pActor->finfo);

      if (! effect) {
        eTB_Printf ("No Visual Effect For Actor: %d  (Bitmap: %d - Flags: %8X)\n", idx, pActor->finfo, pActor->flags);
        ++pActor;
        continue;
      }

      float z;

      if (pActor->flags & ACTOR_CEILHANG) {
        z = sector->CeilHt (pActor->x, pActor->y) - effect->height;
      } else {
        z = sector->FloorHt (pActor->x, pActor->y) + sector->height_offset;
      }

      actor = new cli_parse::Ornament ( idx,
                                          pActor->x, pActor->y, z,
                                            effect->height,
                                              pActor->angle,
                                                room->id,
                                                  pActor->finfo,
                                                    effect->frames,
                                                      effect->views );

      id_t effect_id = ((cli_parse::Ornament *)actor)->GetBitmap ();
      //eTB_Printf ("Ornamental Actor...  Bitmap: %d\n", effect_id);

      if (sector)
        sector->AddActor (actor);
      ////if (parent_sector > 0)
        ////room->GetSector (parent_sector)->AddActor (actor);
    }

    if (room != NULL) {
#if 0
      //eTB_Printf ("Actor: %d is in room: %d, sector: %d\n", idx, room->id, parent_sector);
      if (actor != NULL) {
        if (parent_sector > 0) {
          room->GetSector (parent_sector)->AddActor (actor);
        }
      }
#endif
    } else {
      eTB_Printf ("Actor: %d is in an invalid sector/room...\n", idx);
      //delete actor;
    }
    ++pActor;
  }

  lvls.push_back (this);

  return true;
}



void QueryHardwareInfo (void)
{

}


bool
Underlight::File::Load (int num)
{
  Level*     level;
  int        level_id;
  char*      lvl_data = NULL;
  int        size;
  LevelData* data = NULL;

  int    i;
  bool ret = false;

  if (! _file)
    _file = new eDiskFile ();
    ////_file = new eMemoryMappedFile ();
  else
    _file->close ();

  eStringA _file_name (file_name);
  _file->open (_file_name, _T ("rbS"));

  if (! _file) {
    return ret;
  }

  if (_file->read (&_header, sizeof (FileHeader), 1) == (size_t)EOF) {
    goto fatal_load_error; // In lieu of a try/catch mechanism
  }

  num_levels = _header.num_levels;

  for (i = 0; i < num_levels; i++) {
    FileLevel file_level;

    LevelHeader& header = file_level._header;
    if (! _file->read (&header, sizeof (LevelHeader), 1)) {
      _file->close ();
      return ret;
    }

    decrypt (header.name, LEVEL_NAME_SIZE);

    eTB_Printf ("Level: %d (0x%02x)\n\t>> ",
                  header.level_id,
                    header.checksum1);
    eTB_Printf ("Data Offset:  %#+8d",
                  header.file_position);

    eTB_Printf ("\n\t>> Level Name:  %s\n",
                  header.name);

    eTB_Printf ("\n\n");

    _levels.push_back (file_level);
  }

  level_id = num - 1;//cli_parse::states->cl_level_num - 1;
  if (level_id < 0 ||
      level_id > num_levels) {
    goto fatal_load_error; // In lieu of a try/catch mechanism
  }

  _file->seek (_levels [level_id]._header.file_position, eFileSeekOp::Set);

  if (! _file->read (&_levels [level_id]._data, sizeof (LevelData), 1))
  {
    goto fatal_load_error; // In lieu of a try/catch mechanism
  }

  data = &_levels [level_id]._data;
  size = data->num_lines     * sizeof (FileLinedef) +
         data->num_ornaments * sizeof (Actor)       +
         data->num_rooms     * sizeof (FileRoom)    +
         data->num_sectors   * sizeof (FileSector)  +
         data->num_vertices  * sizeof (VertexData);

  lvl_data = new char [size];

  if (! _file->read (lvl_data, size, 1))
  {
    goto fatal_load_error; // In lieu of a try/catch mechanism
  }

  level = new Level ();
  ret   = level->Load (lvl_data, _levels [level_id]);

  lvl   = level;

  delete [] lvl_data;

  return ret;


fatal_load_error:

  _file->close ();

  if (lvl_data != NULL) {
    delete [] lvl_data;
    lvl_data = NULL;
  }

  return false;
};


Level*
Underlight::GetLevel (int num)
{
  std::vector<Level*>::iterator level = lvls.begin ();
  std::vector<Level*>::iterator end   = lvls.end   ();

  while (eSTL::IterValid (level, end)) {
    if ((*level)->id == num)
      return (*level);

    level++;
  }

  return NULL;
}

bool
Underlight::IsLevelLoaded (int num)
{
  if (GetLevel (num))
    return true;

  // TODO: Add extra checks...
  return false;
}


size_t
Underlight::Level::MemSize (void)
{
  size_t size = sizeof (Underlight::Level);

  size += sizeof (short) * textures.size ();
  size += sizeof (short) * flats.size    ();

  size += sizeof (cli_parse::EntryPoint) * entry_points.size ();

  std::vector<Underlight::Room*>::iterator room = rooms.begin ();
  std::vector<Underlight::Room*>::iterator end  = rooms.end   ();

  while (eSTL::IterValid (room, end)) {
    size += (*room)->MemSize ();

    room++;
  }

  return size;
}



void
Underlight::Level::AddSector (Sector* sector)
{
  Assert (sector->id < sectors.max_size (), _T ("Too many sectors!"));
  sectors [sector->id] = sector;
}

Sector*
Underlight::Level::GetSector (int id)
{
  Assert (id < sectors.max_size (), _T ("Invalid sector"));

  return sectors [id];
}

/* Return NULL if no sector is found and must_exist is true. */
Sector*
Underlight::Level::FindSector ( float   x,
                                float   y,
                                Sector* oldsector,
                                bool    set_anywhere,
                                bool    must_exist )
{
  // Colors: 0=white, 1=gray, 2=black
  static unsigned char SectorColors [MAX_SECTORS];

  // Queue of sectors indexes to check
  static short         SectorQueue  [MAX_SECTORS];

  unsigned int curr; // Index into SectorQueue for current sector
  unsigned int top;  // Highest index into sector queue
  unsigned int sectornum;
  bool found = false;
  Line   *line;
  Sector *pSec;

  // Zero out the colors
  memset (SectorColors, 0, MAX_SECTORS);
  sectornum = 0;
  top       = 0;

  if (oldsector == NULL) // Don't know current sector...
  {
    curr = 1; // Set curr>top so bfs loop won't run
  }
  else
  {
    curr            = 0;
    SectorQueue [0] = oldsector->id;
  }


  // Do a breadth first search of all the sectors, starting
  // with the current sector and adding all facing sectors
  // in turn.

  while (curr <= top)
  {
    sectornum       = SectorQueue [curr];
    Sector* pCurSec = GetSector (sectornum);

    SectorColors [sectornum] = 2; // Set to black
    if (pCurSec == NULL)
    {
      curr++;
      continue;
    }

    if (pCurSec->PointInside (x, y))
    {
      found = true;
      break;
    }

    line = pCurSec->firstline;
    if (line) {
      do {
        if ((line->facingsector != NULL) &&
            (SectorColors [line->facingsector->id] == 0))
        { // Add to queue if it's white
          top++;
          SectorQueue  [top]                    = line->facingsector->id;
          SectorColors [line->facingsector->id] = (unsigned char)1; // Set to gray
        }
      } while (line = line->nextline);
    }

    curr++;
  }

  if (! found)
  { // Didn't find via bfs; try looping...

    /* Andon's Optimization
       --------------------  (5/19/2010)

         Only search sectors that are in the same room as the camera (x,y)...
     */
    //Room* room = room_from_pos (x, y);

    /* If no room, either return sector 0 or NULL, depending on (must_exist).*/
    //if (room != NULL) {
      //size_t num_sectors = room->sectors.size ();
      size_t num_sectors = sectors.size ();

      // eTB_Printf("looking up via exhaustive search...\n");
      for (size_t sectoridx = (num_sectors - 1); sectoridx > 0; sectoridx--)
      {
        pSec = GetSector (sectoridx);//room->sectors [sectoridx];
        if (pSec != NULL && pSec->firstline) {
          if (set_anywhere) {
            if (pSec->PointInside (x, y))
            {
              found     = true;
              sectornum = pSec->id;
              break;
            }
          }
        }
      }
    //}
  }

  if (! found) {
    pSec = NULL;

    /* If the sector MUST EXIST, then situations where no match is
         found will return NULL (instead of the default - sector 0) */
    if (must_exist)
      return NULL;
  }

  //eTB_Printf("returning: %d\n",sectornum);
  return GetSector (sectornum);
}
